/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "boot.h"
#include "vm.h"

#include "cpu.h"
#define CACHE_LINE_COUNT            (32)

#define CACHE_TAG_RAM_START_ADDR    (0xFC000UL)
#define CACHE_TAG_RAM_START_SIZE    (0x1C00UL)

#define FLASH_CPU_ADDR_END         (0x1FFFFFFUL)

#define READ_NORMAL_MODE                        0b0000  // CMD 1bit, ADDR 1bit clk < 30MHz
#define FAST_READ_NORMAL_MODE                   0b0001  // CMD 1bit, ADDR 1bit
#define FAST_READ_DUAL_IO_NORMAL_READ_MODE      0b0100  // CMD 1bit, ADDR 2bit
#define FAST_READ_DUAL_IO_CONTINUOUS_READ_MODE  0b0110  //  no cmd addr 2bit
#define FAST_READ_DUAL_OUTPUT_MODE              0b0010  // cmd 1bit addr 1bit
#define FAST_READ_QUAD_OUTPUT_MODE              0b0011  // cmd 1bit addr 1bit
#define FAST_READ_QUAD_IO_NORMAL_READ_MODE      0b0101  // cmd 1bit addr 4bit
#define FAST_READ_QUAD_IO_CONTINUOUS_READ_MODE  0b0111  // no cmd addr 4bit


#ifdef APP_USE_SOFT_SPI_ACCESS_VM
#define VM_USE_SOFT_SPI     1
#else
#define VM_USE_SOFT_SPI     0
#endif
// | func\port |  A   |  B   |
// |-----------|------|------|
// | CS        | PD3  | PA13 |
// | CLK       | PD0  | PD0  |
// | DO(D0)    | PD1  | PD1  |
// | DI(D1)    | PD2  | PA14 |
// | WP(D2)    | PB7  | PA15 |
// | HOLD(D3)  | PD5  | PD5 |
// set clk
#define X_CLK_BIT           BIT(0)
#define X_CLK_PORT(x)       JL_PORTD->x
// set d0
#define X_D0_BIT            BIT(1)
#define X_D0_PORT(x)        JL_PORTD->x

// -----------TEAM A spi io set
// set d1
#define A_D1_BIT            BIT(2)
#define A_D1_PORT(x)        JL_PORTD->x
// set d2
#define A_D2_BIT            BIT(7)
#define A_D2_PORT(x)        JL_PORTB->x
// set d3
#define A_D3_BIT            BIT(5)
#define A_D3_PORT(x)        JL_PORTD->x

// -----------TEAM B spi io set
// set d1
#define B_D1_BIT            BIT(14)
#define B_D1_PORT(x)        JL_PORTA->x
// set d2
#define B_D2_BIT            BIT(15)
#define B_D2_PORT(x)        JL_PORTA->x
// set d3
#define B_D3_BIT            BIT(5)
#define B_D3_PORT(x)        JL_PORTD->x

struct _dat_bit {
    u8 bit0 : 1;
    u8 bit1 : 1;
    u8 bit2 : 1;
    u8 bit3 : 1;
    u8 bit4 : 1;
    u8 bit5 : 1;
    u8 bit6 : 1;
    u8 bit7 : 1;
};

extern u32 _SPI_CODE_START;
extern u32 _SPI_CODE_END;

volatile static u8 sfc_is_busy;     // 1:  busy

struct flash_info {
    u8 flash_uuid[16];
    u8 init;
    u8 spi_port;
    u32 flash_size;
    u32 flash_capacity;
    u32 flash_id;
    struct vm_info vm;
    struct sfc_info sfc;
};

static int  norflash_dev;

static struct flash_info flash_info = {0};

void doe(u16 k, void *pBuf, u32 lenIn, u32 addr);

////////////////////////////////////spi////////////////////////////
AT_RAM
static void sfc_nop_delay(volatile u32 t)
{
}

AT_RAM
static void spi_cs(u8 cs)
{
    if (flash_info.spi_port == 0) {
        JL_PORTD->DIR &= ~BIT(3);
        cs ? (JL_PORTD->OUT |= BIT(3)) : (JL_PORTD->OUT &= ~BIT(3));
    } else {
        JL_PORTA->DIR &= ~BIT(13);
        cs ? (JL_PORTA->OUT |= BIT(13)) : (JL_PORTA->OUT &= ~BIT(13));
    }

    sfc_nop_delay(10);
}

AT_SPI_CODE
static void spi_wait_ok(void)
{
#if VM_USE_SOFT_SPI
#else
    asm volatile("csync");
    while (!(JL_SPI0->CON & BIT(15)));
    JL_SPI0->CON |= BIT(14);
#endif
}

AT_RAM
void spi_soft_writebyte(u8 dat)
{
    u32 D0_PORT = X_D0_PORT(OUT) & (~X_D0_BIT);
    X_D0_PORT(DIR) &= ~X_D0_BIT;
    X_D0_PORT(OUT) =  D0_PORT | ((!!(dat & 0b10000000) * X_D0_BIT));
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) =  D0_PORT | ((!!(dat & 0b1000000) * X_D0_BIT));
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) =  D0_PORT | ((!!(dat & 0b100000) * X_D0_BIT));
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) =  D0_PORT | ((!!(dat & 0b10000) * X_D0_BIT));
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) =  D0_PORT | ((!!(dat & 0b1000) * X_D0_BIT));
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) =  D0_PORT | ((!!(dat & 0b100) * X_D0_BIT));
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) =  D0_PORT | ((!!(dat & 0b10) * X_D0_BIT));
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) =  D0_PORT | ((!!(dat & 1) * X_D0_BIT));
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) &= ~X_D0_BIT;
}

AT_SPI_CODE
static void spi_writebyte(u8 dat)
{
#if VM_USE_SOFT_SPI
    spi_soft_writebyte(dat);
#else
    JL_SPI0->CON |= BIT(3);  // BIDIR
    JL_SPI0->CON &= ~BIT(12);  // DIR
    JL_SPI0->BUF = dat;
    spi_wait_ok();
    JL_SPI0->CON &= ~BIT(3);  // BIDIR
#endif
}

AT_SPI_CODE
static void spi_writebyte_dma(u8 dat)
{
#if VM_USE_SOFT_SPI
    spi_writebyte(dat);
#else
    JL_SPI0->BUF = dat;
    spi_wait_ok();
#endif
}

AT_SPI_CODE
static void spi_write_dma_1bit(u8 *dat, u32 len)
{
#if VM_USE_SOFT_SPI
    for (u32 i = 0; i < len; i++) {
        spi_writebyte(*(dat + i));
    }
#else
    JL_SPI0->ADR = (u32)dat;
    JL_SPI0->CNT = len;
    spi_wait_ok();
#endif
}

AT_RAM
u8 spi_soft_readbyte(void)
{
    u8 ret_byte;
    u8 i;
    if (flash_info.spi_port == 0) {
        A_D1_PORT(DIR) |=  A_D1_BIT;
        for (i = 0; i < 8; i++) {
            X_CLK_PORT(OUT) |=  X_CLK_BIT;
            ret_byte = ret_byte << 1;
            if (A_D1_PORT(IN) & A_D1_BIT) {
                ret_byte ++;
            }
            X_CLK_PORT(OUT) &= ~X_CLK_BIT;
        }
    } else {
        B_D1_PORT(DIR) |=  B_D1_BIT;
        for (i = 0; i < 8; i++) {
            X_CLK_PORT(OUT) |=  X_CLK_BIT;
            ret_byte = ret_byte << 1;
            if (B_D1_PORT(IN) & B_D1_BIT) {
                ret_byte ++;
            }
            X_CLK_PORT(OUT) &= ~X_CLK_BIT;
        }
    }
    return ret_byte;
}

AT_SPI_CODE
static u8 spi_readbyte(void)
{
    u8 ret_byte;
#if VM_USE_SOFT_SPI
    ret_byte = spi_soft_readbyte();
#else
    JL_SPI0->CON |= BIT(3);  // BIDIR
    JL_SPI0->CON |= BIT(12);  // DIR
    JL_SPI0->BUF = 0xff;
    spi_wait_ok();
    ret_byte = JL_SPI0->BUF;
    JL_SPI0->CON &= ~BIT(3);  // BIDIR
#endif

    return ret_byte;
}

AT_SPI_CODE
static u8 spi_readbyte_dma(void)
{
    u8 ret_byte;
#if VM_USE_SOFT_SPI
    ret_byte = spi_readbyte();
#else
    JL_SPI0->BUF = 0xff;
    spi_wait_ok();
    ret_byte = JL_SPI0->BUF;
#endif
    return ret_byte;
}

AT_SPI_CODE
static s32 spi_write_dma(u8 *buf, u16 len)
{
#if VM_USE_SOFT_SPI
    u32 spi_mode = READ_NORMAL_MODE;
#else
    u32 spi_mode = (JL_SFC->CON >> 8) & 0xf;
#endif
    // 1bit          2bit       4bit
    // 0b0000       0b0010     0b0011
    // 0b0001       0b0100     0b0101
    //              0b0110     0b0111
    //              0b1000     0b1001

#if VM_USE_SOFT_SPI
#else
    JL_SPI0->CON &= ~BIT(12);  // DIR
#endif

    switch (spi_mode) {
        case FAST_READ_QUAD_OUTPUT_MODE:
        case FAST_READ_QUAD_IO_NORMAL_READ_MODE:
        case FAST_READ_QUAD_IO_CONTINUOUS_READ_MODE:
        case 0b1001:
#if VM_USE_SOFT_SPI
#else
            JL_SPI0->CON |= BIT(11); // 4bit mode
#endif
            break;
        case FAST_READ_DUAL_OUTPUT_MODE:
        case FAST_READ_DUAL_IO_NORMAL_READ_MODE:
        case FAST_READ_DUAL_IO_CONTINUOUS_READ_MODE:
        case 0b1000:
#if VM_USE_SOFT_SPI
#else
            JL_SPI0->CON |= BIT(10); // 2bit moode
#endif
            break;
        default:
            // 1bit mode
            break;
    }

    while (len--) {
        spi_writebyte_dma(*buf++);
    }

#if VM_USE_SOFT_SPI
#else
    JL_SPI0->CON &= ~(BIT(10) | BIT(11)) ; // 1bit
#endif

    return 0;
}

AT_SPI_CODE
static s32 spi_read_dma(u8 *buf, u16 len)
{
#if VM_USE_SOFT_SPI
    u32 spi_mode = READ_NORMAL_MODE;
#else
    u32 spi_mode = (JL_SFC->CON >> 8) & 0xf;
#endif
    // 1bit          2bit       4bit
    // 0b0000       0b0010     0b0011
    // 0b0001       0b0100     0b0101
    //              0b0110     0b0111
    //              0b1000     0b1001

#if VM_USE_SOFT_SPI
#else
    JL_SPI0->CON |= BIT(12);  // DIR
#endif

    switch (spi_mode) {
        case FAST_READ_QUAD_OUTPUT_MODE:
        case FAST_READ_QUAD_IO_NORMAL_READ_MODE:
        case FAST_READ_QUAD_IO_CONTINUOUS_READ_MODE:
        case 0b1001:
#if VM_USE_SOFT_SPI
#else
            JL_SPI0->CON |= BIT(11); // 4bit mode
#endif
            break;
        case FAST_READ_DUAL_OUTPUT_MODE:
        case FAST_READ_DUAL_IO_NORMAL_READ_MODE:
        case FAST_READ_DUAL_IO_CONTINUOUS_READ_MODE:
        case 0b1000:
#if VM_USE_SOFT_SPI
#else
            JL_SPI0->CON |= BIT(10); // 2bit moode
#endif
            break;
        default:
            // 1bit mode
            break;
    }

    while (len--) {
        *buf = spi_readbyte_dma();
        buf++;
    }

#if VM_USE_SOFT_SPI
#else
    JL_SPI0->CON &= ~(BIT(10) | BIT(11));
#endif
    return 0;
}

AT_SPI_CODE
static u32 flash_addr2cpu_addr(u32 offset)
{
    offset -= flash_info.sfc.sfc_base_addr;

    return flash_info.sfc.app_addr + offset;
}

AT_SPI_CODE
static u32 cpu_addr2flash_addr(u32 offset)
{
    return flash_info.sfc.sfc_base_addr + (offset - flash_info.sfc.app_addr);
}

/* AT_SPI_CODE */
_INLINE_ u32 get_sfc_status(void)
{
    return sfc_is_busy;
}

/* AT_SPI_CODE */
_INLINE_ void sfc_protect(u8 mode)
{
    sfc_is_busy = mode;
}

static int sfc_norflash_mutex;
#define sfc_norflash_mutex_init()   os_mutex_create(&sfc_norflash_mutex)
#define sfc_norflash_mutex_enter()  os_mutex_pend(&sfc_norflash_mutex, 0)
#define sfc_norflash_mutex_exit()   os_mutex_post(&sfc_norflash_mutex);

AT_SPI_CODE
static void load_spi_code2cache()
{
}

/* api at maskrom */
void flushinv_dcache(void *ptr, int len);
void sfc_drop_cache(void *ptr, u32 len);
void sfc_resume(u32 disable_spi);

AT_RAM
void sfc_suspend(u32 enable_spi)
{
    u8 port = spi_get_port();
    // wait cache idle
    while (!(JL_DSP->CON & BIT(5)));
    // wait sfc idle
    while (JL_SFC->CON & BIT(31));

    if (port == 0) {
        JL_PORTD->PU  |=  BIT(3);
        JL_PORTD->DIR |=  BIT(3);
    } else {
        JL_PORTA->PU  |=  BIT(13);
        JL_PORTA->DIR |=  BIT(13);
    }

    // disable sfc
    JL_SFC->CON &= ~BIT(0);

    if (port == 0) {
        JL_PORTD->OUT |=  BIT(3);
        JL_PORTD->DIR &= ~BIT(3);
        JL_PORTD->PU  &= ~BIT(3);
    } else {
        JL_PORTA->OUT |=  BIT(13);
        JL_PORTA->DIR &= ~BIT(13);
        JL_PORTA->PU  &= ~BIT(13);
    }

    if (enable_spi) {
        JL_SPI0->CON |= BIT(0);
    }
}
#define TAG_RAM_START_ADDR       0xfc000L
#define TAG_RAM_SIZE             0x00800L

AT_RAM
void sfc_drop_cache(void *ptr, u32 len)
{
    const u32 dsp_con = JL_DSP->CON;
    JL_DSP->CON &= ~(BIT(8) | BIT(9) | BIT(10));

    u32 *t = (u32 *)TAG_RAM_START_ADDR;
    u8 i, j;
    u32 valid_line_addr_in_cache;

    u32 ptr_align = ((u32)ptr & ~(CACHE_LINE_COUNT - 1));
    u32 t_len = (u32)(ptr - ptr_align);

    if (len > CACHE_LINE_COUNT - t_len) {
        if (t_len) {
            len += t_len;
        }
    }
    len = (len + CACHE_LINE_COUNT - 1) & ~(CACHE_LINE_COUNT - 1);

    for (j = 0; j < 128; j++) {
        for (i = 0; i < 4; i++) {
            valid_line_addr_in_cache = ((*t) & 0x1FFF) * 4096 + j * 0x20;

            if ((valid_line_addr_in_cache <= ptr_align + len) &&
                (valid_line_addr_in_cache + CACHE_LINE_COUNT >= ptr_align)) {
                *t = 0;
            }
            t += 1;
        }
    }

    JL_DSP->CON  = dsp_con;
}

AT_RAM
void spi_cache_way_switch(u8 way_num)
{
    CPU_CRITICAL_ENTER();
    // sfc_suspend
    while (!(JL_DSP->CON & BIT(5)));
    while (JL_SFC->CON & BIT(31));
    JL_SFC->CON &= ~BIT(0);

    // cache disable
    JL_DSP->CON &= ~(BIT(8));
    // clr TAG_RAM
    memset_s((void *)TAG_RAM_START_ADDR, TAG_RAM_SIZE, 0x0, TAG_RAM_SIZE);

    JL_DSP->CON &= ~(BIT(12) | BIT(13));
    JL_DSP->CON |= ((u16)way_num) << 12;

    JL_DSP->CON |= BIT(8);
    // sfc resume
    JL_SFC->CON |= BIT(0);

    CPU_CRITICAL_EXIT();
}

#include "clock_hw.h"

static u32 _this_sys_clk = 48000000;    // 先给一个初值

AT_SPI_CODE
void __udelay(u32 us)
{
    u32 delay_value = us * (_this_sys_clk / 1000000);

    q32DSP(0)->TTMR_CON &= ~BIT(0);

    const u32 ttmr_prd = q32DSP(0)->TTMR_PRD;
    const u32 ttmr_con = q32DSP(0)->TTMR_CON;
    const u32 ttmr_cnt = q32DSP(0)->TTMR_CNT;
    u32 end_cnt = ttmr_cnt + delay_value;

    q32DSP(0)->TTMR_PRD = end_cnt;

    q32DSP(0)->TTMR_CON |=  BIT(6) | BIT(0);

    while (1) {
        if (q32DSP(0)->TTMR_CON & BIT(7)) {
            if ((end_cnt >= ttmr_prd) || (ttmr_con & BIT(7))) {
            } else {
                q32DSP(0)->TTMR_CON |= BIT(6);
            }
            break;
        }
    }

    q32DSP(0)->TTMR_PRD = ttmr_prd;
}

AT_RAM
void spi_for_maskrom_init(void)
{
    LSB_CLK_DIV(3);
    sfc_suspend(0);
}

AT_SPI_CODE
static void enter_spi_code(void)
{
    CPU_CRITICAL_ENTER();
    _this_sys_clk = clk_get("sys");

    sfc_protect(1);
#if VM_USE_SOFT_SPI
    sfc_suspend(0);
#else
    sfc_suspend(1);
#endif
}

AT_SPI_CODE
static void exit_spi_code(u32 lock)
{
#if VM_USE_SOFT_SPI
    sfc_resume(0);
#else
    sfc_resume(1);
#endif

    sfc_protect(lock);

    CPU_CRITICAL_EXIT();
}

AT_SPI_CODE
static void norflash_write_enable()
{
    spi_cs(0);
    spi_writebyte(0x06);
    spi_cs(1);
}

AT_SPI_CODE
static void norflash_send_addr(u32 addr)
{
    spi_writebyte(addr >> 16);
    spi_writebyte(addr >> 8);
    spi_writebyte(addr);
}

AT_SPI_CODE
static u32 norflash_is_busy()
{
    spi_cs(0);
    spi_writebyte(0x05);
    u32 st = spi_readbyte();
    spi_cs(1);
    return st & BIT(0);
}

AT_SPI_CODE
static void norflash_suspend()
{
    spi_cs(0);
    spi_writebyte(0x75);
    spi_cs(1);
}

AT_SPI_CODE
static void norflash_resume()
{
    spi_cs(0);
    spi_writebyte(0x7A);
    spi_cs(1);
}

AT_SPI_CODE
static void norflash_wait_ok(void)
{
    u32 timeout = 500000;

    while (timeout) {
        spi_cs(0);
        spi_writebyte(0x05);
        u32 reg = spi_readbyte();
        spi_cs(1);

        if (!(reg & 1)) {
            break;
        }
        timeout --;

        __udelay(100);
    }

    if (timeout == 0) {
    }
}

AT_SPI_CODE
static s32 __norflash_read(u8 *buf, u32 addr, u16 len, u32 is_continue_mode)
{
#if VM_USE_SOFT_SPI
    u32 spi_mode = READ_NORMAL_MODE;
#else
    u32 spi_mode = (JL_SFC->CON >> 8) & 0xf;
#endif
    // 1bit          2bit       4bit
    // 0b0000       0b0010     0b0011
    // 0b0001       0b0100     0b0101
    //              0b0110     0b0111
    //              0b1000     0b1001
    u8 addr_and_mode[4];

    switch (spi_mode) {
        case READ_NORMAL_MODE: // WINBOND_READ_DATA_MODE:
            spi_cs(0);
            spi_writebyte(0x03);
            norflash_send_addr(addr);
            spi_read_dma(buf, len);
            spi_cs(1);
            break;

        case FAST_READ_NORMAL_MODE:
            spi_cs(0);
            spi_writebyte(0x0b);
            norflash_send_addr(addr);
            spi_readbyte(); // DUMMY_BYTE
            spi_read_dma(buf, len);
            spi_cs(1);
            break;

        case FAST_READ_DUAL_IO_NORMAL_READ_MODE: // 0b0100:
            addr_and_mode[0] = (u8)(addr >> 16);
            addr_and_mode[1] = (u8)(addr >> 8);
            addr_and_mode[2] = (u8)addr;
            addr_and_mode[3] = 0x00; // WINBOND_CONTINUOUS_READ_NORMAL_MODE;
            spi_cs(0);
            spi_writebyte(0xbb);        //     WINBOND_FAST_READ_DUAL_IO
            spi_write_dma(addr_and_mode, 4);
            spi_read_dma(buf, len);
            spi_cs(1);
            break;
        // 0b0110://WINBOND_FAST_READ_DUAL_IO_CONTINUOUS_READ_MODE: addr 2bit no cmd
        case FAST_READ_DUAL_IO_CONTINUOUS_READ_MODE:
            addr_and_mode[0] = (u8)(addr >> 16);
            addr_and_mode[1] = (u8)(addr >> 8);
            addr_and_mode[2] = (u8)addr;
            addr_and_mode[3] = 0xa0 ; // WINBOND_CONTINUOUS_READ_ENHANCE_MODE;
            spi_cs(0);
            if (is_continue_mode) {
                spi_writebyte(0xBB);
            }
            spi_write_dma(addr_and_mode, 4);
            spi_read_dma(buf, len);
            spi_cs(1);
            break;

        case FAST_READ_DUAL_OUTPUT_MODE: // 0b0010://WINBOND_FAST_READ_DUAL_OUTPUT_MODE:
            spi_cs(0);
            spi_writebyte(0x3b);
            norflash_send_addr(addr);
            spi_read_dma(addr_and_mode, 2); // 8 dummy clocks
            spi_read_dma(buf, len);
            spi_cs(1);
            break;

        case FAST_READ_QUAD_OUTPUT_MODE: // 0b0011://WINBOND_FAST_READ_QUAD_OUTPUT_MODE:
            spi_cs(0);
            spi_writebyte(0x6b);
            norflash_send_addr(addr);
            spi_read_dma(addr_and_mode, 4); // 8 dummy clocks
            spi_read_dma(buf, len);
            spi_cs(1);
            break;

        case FAST_READ_QUAD_IO_NORMAL_READ_MODE: // 0b0101://WINBOND_FAST_READ_QUAD_IO_NORMAL_READ_MODE:
            addr_and_mode[0] = (u8)(addr >> 16);
            addr_and_mode[1] = (u8)(addr >> 8);
            addr_and_mode[2] = (u8)addr;
            addr_and_mode[3] = 0x00; // WINBOND_CONTINUOUS_READ_NORMAL_MODE;
            spi_cs(0);
            spi_writebyte(0xeb);
            spi_write_dma(addr_and_mode, 4);
            spi_read_dma(addr_and_mode, 2);  // 4 dummy clocks
            spi_read_dma(buf, len);
            spi_cs(1);
            break;

        case FAST_READ_QUAD_IO_CONTINUOUS_READ_MODE: // 0b0111://WINBOND_FAST_READ_QUAD_IO_CONTINUOUS_READ_MODE:
            addr_and_mode[0] = (u8)(addr >> 16);
            addr_and_mode[1] = (u8)(addr >> 8);
            addr_and_mode[2] = (u8)addr;
            addr_and_mode[3] = 0xa0; // WINBOND_CONTINUOUS_READ_ENHANCE_MODE;
            spi_cs(0);
            if (is_continue_mode) {
                spi_writebyte(0xEB);
            }
            spi_write_dma(addr_and_mode, sizeof(addr_and_mode));
            spi_read_dma(addr_and_mode, 2);  // 4 dummy clocks
            spi_read_dma(buf, len);
            spi_cs(1);
            break;

        default:
            return -1;
    }
    return len;
}

AT_SPI_CODE
void flash_exit_4bit_mode_by_spi_soft(void)
{
    spi_cs(0);
    if (flash_info.spi_port == 0) {
        A_D1_PORT(OUT) |=  A_D1_BIT;
        A_D1_PORT(DIR) &= ~A_D1_BIT;
        A_D2_PORT(OUT) |=  A_D2_BIT;
        A_D2_PORT(DIR) &= ~A_D2_BIT;
        A_D3_PORT(OUT) |=  A_D3_BIT;
        A_D3_PORT(DIR) &= ~A_D3_BIT;
    } else {
        B_D1_PORT(OUT) |=  B_D1_BIT;
        B_D1_PORT(DIR) &= ~B_D1_BIT;
        B_D2_PORT(OUT) |=  B_D2_BIT;
        B_D2_PORT(DIR) &= ~B_D2_BIT;
        B_D3_PORT(OUT) |=  B_D3_BIT;
        B_D3_PORT(DIR) &= ~B_D3_BIT;
    }
    X_D0_PORT(OUT) |=  X_D0_BIT;
    X_D0_PORT(DIR) &= ~X_D0_BIT;
    for (u8 i = 0; i < 8; i ++) {
        X_CLK_PORT(OUT) |=  X_CLK_BIT;
        X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    }
    if (flash_info.spi_port == 0) {
        A_D1_PORT(DIR) |=  A_D1_BIT;
    } else {
        B_D1_PORT(DIR) |=  B_D1_BIT;
    }
    X_D0_PORT(OUT) &= ~X_D0_BIT;
    spi_cs(1);
}

AT_SPI_CODE
void flash_exit_2bit_mode_by_spi_soft(void)
{
    spi_cs(0);
    if (flash_info.spi_port == 0) {
        A_D1_PORT(OUT) |=  A_D1_BIT;
        A_D1_PORT(DIR) &= ~A_D1_BIT;
    } else {
        B_D1_PORT(OUT) |=  B_D1_BIT;
        B_D1_PORT(DIR) &= ~B_D1_BIT;
    }
    X_D0_PORT(OUT) |=  X_D0_BIT;
    X_D0_PORT(DIR) &= ~X_D0_BIT;
    for (u8 i = 0; i < 8; i ++) {
        X_CLK_PORT(OUT) |=  X_CLK_BIT;
        X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    }
    if (flash_info.spi_port == 0) {
        A_D1_PORT(DIR) |=  A_D1_BIT;
    } else {
        B_D1_PORT(DIR) |=  B_D1_BIT;
    }
    X_D0_PORT(OUT) &= ~X_D0_BIT;
    spi_cs(1);
}

AT_SPI_CODE
int exit_continue_mode()
{
    u32 spi_mode = (JL_SFC->CON >> 8) & 0xf;
    u32 ret = 0;
    if (spi_mode == 0b0111) { // 4bit mode no cmd
#if VM_USE_SOFT_SPI
        flash_exit_4bit_mode_by_spi_soft();
#else
        spi_cs(0);
        spi_writebyte(0xff);
        spi_writebyte(0xff);
        spi_writebyte(0xff);
        spi_writebyte(0xff);
        spi_cs(1);
#endif
        ret = 1;
    } else if (spi_mode == 0b0110) { // 2bit mode no cmd
#if VM_USE_SOFT_SPI
        flash_exit_2bit_mode_by_spi_soft();
#else
        spi_cs(0);
        spi_writebyte(0xff);
        spi_writebyte(0xff);
        spi_cs(1);
#endif
        ret = 1;
    }
    return ret;
}

AT_SPI_CODE
void flash_enter_4bit_mode_by_spi_soft(void)
{
    spi_cs(0);
    spi_soft_writebyte(0xEB);
    if (flash_info.spi_port == 0) {
        A_D1_PORT(OUT) &= ~A_D1_BIT;
        A_D1_PORT(DIR) &= ~A_D1_BIT;
        A_D2_PORT(OUT) &= ~A_D2_BIT;
        A_D2_PORT(DIR) &= ~A_D2_BIT;
        A_D3_PORT(OUT) &= ~A_D3_BIT;
        A_D3_PORT(DIR) &= ~A_D3_BIT;
    } else {
        B_D1_PORT(OUT) &= ~B_D1_BIT;
        B_D1_PORT(DIR) &= ~B_D1_BIT;
        B_D2_PORT(OUT) &= ~B_D2_BIT;
        B_D2_PORT(DIR) &= ~B_D2_BIT;
        B_D3_PORT(OUT) &= ~B_D3_BIT;
        B_D3_PORT(DIR) &= ~B_D3_BIT;
    }
    X_D0_PORT(OUT) &= ~X_D0_BIT;
    X_D0_PORT(DIR) &= ~X_D0_BIT;
    for (u8 i = 0; i < 6; i ++) {
        X_CLK_PORT(OUT) |=  X_CLK_BIT;
        X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    }
    X_D0_PORT(OUT) &= ~X_D0_BIT;
    if (flash_info.spi_port == 0) {
        A_D1_PORT(OUT) |=  A_D1_BIT;
        A_D2_PORT(OUT) &= ~A_D2_BIT;
        A_D3_PORT(OUT) |=  A_D3_BIT;
    } else {
        B_D1_PORT(OUT) |=  B_D1_BIT;
        B_D2_PORT(OUT) &= ~B_D2_BIT;
        B_D3_PORT(OUT) |=  B_D3_BIT;
    }
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) &= ~X_D0_BIT;
    if (flash_info.spi_port == 0) {
        A_D1_PORT(OUT) &= ~A_D1_BIT;
        A_D2_PORT(OUT) &= ~A_D2_BIT;
        A_D3_PORT(OUT) &= ~A_D3_BIT;
    } else {
        B_D1_PORT(OUT) &= ~B_D1_BIT;
        B_D2_PORT(OUT) &= ~B_D2_BIT;
        B_D3_PORT(OUT) &= ~B_D3_BIT;
    }
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    if (flash_info.spi_port == 0) {
        A_D1_PORT(DIR) |=  A_D1_BIT;
        A_D2_PORT(OUT) |=  A_D2_BIT;
        A_D3_PORT(OUT) |=  A_D3_BIT;
    } else {
        B_D1_PORT(DIR) |=  B_D1_BIT;
        B_D2_PORT(OUT) |=  B_D2_BIT;
        B_D3_PORT(OUT) |=  B_D3_BIT;
    }
    for (u8 i = 0; i < 8; i ++) {
        X_CLK_PORT(OUT) |=  X_CLK_BIT;
        X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    }
    spi_cs(1);
}

AT_SPI_CODE
void flash_enter_2bit_mode_by_spi_soft(void)
{
    spi_cs(0);
    spi_soft_writebyte(0xBB);
    if (flash_info.spi_port == 0) {
        A_D1_PORT(OUT) &= ~A_D1_BIT;
        A_D1_PORT(DIR) &= ~A_D1_BIT;
    } else {
        B_D1_PORT(OUT) &= ~B_D1_BIT;
        B_D1_PORT(DIR) &= ~B_D1_BIT;
    }
    X_D0_PORT(OUT) &= ~X_D0_BIT;
    X_D0_PORT(DIR) &= ~X_D0_BIT;
    for (u8 i = 0; i < 12; i ++) {
        X_CLK_PORT(OUT) |=  X_CLK_BIT;
        X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    }
    X_D0_PORT(OUT) &= ~X_D0_BIT;
    if (flash_info.spi_port == 0) {
        A_D1_PORT(OUT) |=  A_D1_BIT;
    } else {
        B_D1_PORT(OUT) |=  B_D1_BIT;
    }
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_D0_PORT(OUT) &= ~X_D0_BIT;
    if (flash_info.spi_port == 0) {
        A_D1_PORT(OUT) &= ~A_D1_BIT;
    } else {
        B_D1_PORT(OUT) &= ~B_D1_BIT;
    }
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    X_CLK_PORT(OUT) |=  X_CLK_BIT;
    X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    if (flash_info.spi_port == 0) {
        A_D1_PORT(DIR) |=  A_D1_BIT;
    } else {
        B_D1_PORT(DIR) |=  B_D1_BIT;
    }
    for (u8 i = 0; i < 8; i ++) {
        X_CLK_PORT(OUT) |=  X_CLK_BIT;
        X_CLK_PORT(OUT) &= ~X_CLK_BIT;
    }
    spi_cs(1);
}

AT_SPI_CODE
void enter_continue_mode()
{
#if VM_USE_SOFT_SPI
    u32 spi_mode = (JL_SFC->CON >> 8) & 0xf;
    u8 i;
    switch (spi_mode) {
        case FAST_READ_QUAD_IO_CONTINUOUS_READ_MODE: // 0b0111://WINBOND_FAST_READ_QUAD_IO_CONTINUOUS_READ_MODE:
            flash_enter_4bit_mode_by_spi_soft();
            break;
        // 0b0110://WINBOND_FAST_READ_DUAL_IO_CONTINUOUS_READ_MODE: addr 2bit no cmd
        case FAST_READ_DUAL_IO_CONTINUOUS_READ_MODE:
            flash_enter_2bit_mode_by_spi_soft();
            break;
    }
#else
    u32 tmp;
    __norflash_read((void *)&tmp, 0, 1, 1);
#endif
}

AT_SPI_CODE
static int _norflash_read(u8 *buf, u32 offset, u32 len)
{
    enter_spi_code();
#if VM_USE_SOFT_SPI
    u32 is_no_cmd = exit_continue_mode();
#endif

    __norflash_read(buf, offset, len, 0);

#if VM_USE_SOFT_SPI
    if (is_no_cmd) {
        enter_continue_mode();
    }
#endif

    exit_spi_code(0);
    return len;
}

int norflash_origin_read(u8 *buf, u32 offset, u32 len)
{
    return _norflash_read(buf, offset, len);
}

static u32 cache_used_check()
{
    if ((u32)&_SPI_CODE_END - (u32)&_SPI_CODE_START > 4 * 1024) {
        return 1;
    }
    return 0;
}
int norflash_init(const struct dev_node *node, void *arg)
{
    if (flash_info.init) {
        return 0;
    }

    if (JL_IOMAP->CON1 & BIT(5)) {  // 判断SFC用哪一组
        // SPI PORT B
        flash_info.spi_port = 1;
    } else {
        // SPI PORT A
        flash_info.spi_port = 0;
    }
#if VM_USE_SOFT_SPI
#else
    JL_SPI0->CON &= ~BIT(3);  // DIR
    JL_SPI0->CON &= ~(BIT(10) | BIT(11));
    JL_IOMAP->CON1 &= ~BIT(1); // do &di disable
#endif
    sfc_norflash_mutex_init();

    flash_info.init = 1;
    return cache_used_check();
}
int norflash_open(const char *name, struct device **device, void *arg)
{
    if (!flash_info.init) {
        norflash_init(NULL, NULL);
    }

    return cache_used_check();
}
int norflash_read(struct device *device, void *buf, u32 len, u32 offset)
{
    if (offset < flash_info.sfc.sfc_base_addr) {
        sfc_norflash_mutex_enter();
        _norflash_read(buf, offset, len);
        doe(0xffff, buf, len, 0);
        sfc_norflash_mutex_exit();
    } else {
        memcpy_s(buf, len, (const void *)flash_addr2cpu_addr(offset), len);
    }
    return len;
}

AT_SPI_CODE
static int _norflash_write(u8 *buf, u32 offset, u32 len, u8 dma_mode)
{
    u32 _len = len;

    if (len == 0) {
        return 0;
    }
    enter_spi_code();
    u32 is_no_cmd = exit_continue_mode();

    norflash_write_enable();

    spi_cs(0);
    spi_writebyte(0x02);

    norflash_send_addr(offset);

    if (dma_mode) {
        spi_write_dma_1bit(buf, len);
    } else {
        while (len--) {
            spi_writebyte(*buf++);
        }
    }

    spi_cs(1);

    sfc_drop_cache((u8 *)flash_addr2cpu_addr(offset), _len);
    norflash_wait_ok();

    if (is_no_cmd) {
        enter_continue_mode();
    }

    exit_spi_code(0);
    return 0;
}
int norflash_write(struct device *device, void *buf, u32 len, u32 offset)
{
#if (USE_SDFILE_NEW == 1)
    // 新文件系统默认保护key区域
    if ((offset + len) > flash_info.flash_capacity) {
        return 0;
    }
#else
    if (flash_info.flash_size > 256 * 1024) {
        if ((offset + len) > (flash_info.flash_size - 4 * 1024)) {
            return 0;
        }
    }
#endif
    sfc_norflash_mutex_enter();
    int _len = len;
    u8 *_buf = (u8 *)buf;
    u32 first_page_len = 256 - (offset % 256);
    first_page_len = _len > first_page_len ? first_page_len : _len;
    _norflash_write(_buf, offset, first_page_len, 0);

    _len -= first_page_len;
    _buf += first_page_len;
    offset += first_page_len;
    while (_len) {
        u32 cnt = _len > 256 ? 256 : _len;
        _norflash_write(_buf, offset, cnt, 0);
        _len -= cnt;
        offset += cnt;
        _buf += cnt;
    }
    sfc_norflash_mutex_exit();
    return len;
}

int norflash_dma_write(struct device *device, void *buf, u32 len, u32 offset)
{
#if (USE_SDFILE_NEW == 1)
    // 新文件系统默认保护key区域
    if ((offset + len) > flash_info.flash_capacity) {
        return 0;
    }
#else
    if (flash_info.flash_size > 256 * 1024) {
        if ((offset + len) > (flash_info.flash_size - 4 * 1024)) {
            return 0;
        }
    }
#endif
    sfc_norflash_mutex_enter();
    int _len = len;
    u8 *_buf = (u8 *)buf;
    u32 first_page_len = 256 - (offset % 256);
    first_page_len = _len > first_page_len ? first_page_len : _len;
    _norflash_write(_buf, offset, first_page_len, 1);

    _len -= first_page_len;
    _buf += first_page_len;
    offset += first_page_len;
    while (_len) {
        u32 cnt = _len > 256 ? 256 : _len;
        _norflash_write(_buf, offset, cnt, 1);
        _len -= cnt;
        offset += cnt;
        _buf += cnt;
    }
    sfc_norflash_mutex_exit();
    return len;
}

AT_SPI_CODE
int norflash_erase(u32 cmd, u32 addr)
{
    u32 len;
    if (cmd == IOCTL_ERASE_SECTOR) {
        cmd = 0x20;
        len = 4 * 1024;
    } else if (cmd == IOCTL_ERASE_BLOCK) {
        cmd = 0xD8;
        len = 64 * 1024;
    } else if (cmd == IOCTL_ERASE_PAGE) {
        cmd = 0x81;
        len = 256;
    } else {
        return -1;
    }

    addr &= ~(len - 1);
#if (USE_SDFILE_NEW == 1)
    // 新文件系统工具默认保护key区域
    u32 protect_addr = flash_info.flash_capacity & (~(len - 1));
    if (addr == protect_addr) {
        return -1;
    }
#else
    if (flash_info.flash_size > 256 * 1024) {
        if ((cmd == 0x20) && (addr == (flash_info.flash_size - 4096))) {
            return -1;
        } else if ((cmd == 0xd8) && (addr == (flash_info.flash_size - 64 * 1024))) {
            return -1;
        }
    }
#endif

    enter_spi_code();
    u32 is_no_cmd = exit_continue_mode();

    norflash_write_enable();

    spi_cs(0);
    spi_writebyte(cmd);
    norflash_send_addr(addr);
    spi_cs(1);

    sfc_drop_cache((u8 *)flash_addr2cpu_addr(addr), len);
    norflash_wait_ok();

    if (is_no_cmd) {
        enter_continue_mode();
    }

    exit_spi_code(0);
    return 0;
}

AT_RAM
void flushinv_dcache_api(u32 addr, u32 len)
{
    enter_spi_code();
    asm("trigger");
    sfc_drop_cache((void *)flash_addr2cpu_addr(addr), len);
    exit_spi_code(0);
}

AT_SPI_CODE
void read_flash_id()
{
    enter_spi_code();
    u32 is_no_cmd = exit_continue_mode();

    u32 i;
    spi_cs(0);
    spi_writebyte(0x9f);
    flash_info.flash_id = 0;
    for (i = 0; i < 3; i++) {
        flash_info.flash_id |= spi_readbyte();
        flash_info.flash_id <<= 8;
    }
    spi_cs(1);

    // read flash uuid
    i = 1000;
    while (i --);
    spi_cs(0);
    spi_writebyte(0x4b);
    spi_writebyte(0);
    spi_writebyte(0);
    spi_writebyte(0);
    spi_writebyte(0);
    for (i = 0; i < 16; i ++) {
        flash_info.flash_uuid[i] = spi_readbyte();
    }
    spi_cs(1);

    if (is_no_cmd) {
        enter_continue_mode();
    }
    flash_info.flash_id >>= 8;
    exit_spi_code(0);
}

u8 *get_norflash_uuid(void)
{
    return ((u8 *)&flash_info.flash_uuid[0]);
}

AT_RAM
static void tmp_udelay(u32 usec)
{
    JL_TIMER0->CON = BIT(14);
    JL_TIMER0->CNT = 0;
    JL_TIMER0->PRD = 4 * usec;
    JL_TIMER0->CON = BIT(4) | BIT(0);
    while ((JL_TIMER0->CON & BIT(15)) == 0);
    JL_TIMER0->CON = BIT(14);
}

AT_RAM
static void ze_entry_tm()
{
    // entry TM
    tmp_udelay(10);
    spi_cs(0);
    spi_soft_writebyte(0x36);
    spi_cs(1);
    tmp_udelay(2);
    spi_cs(0);
    spi_soft_writebyte(0xd7);
    spi_cs(1);
    tmp_udelay(2);
    spi_cs(0);
    spi_soft_writebyte(0xa0);
    spi_cs(1);
    tmp_udelay(2);
}
AT_RAM
static void ze_exit_tm()
{
    // exit  TM
    tmp_udelay(2);
    spi_cs(0);
    spi_soft_writebyte(0x0f);
    spi_soft_writebyte(0x00);
    spi_soft_writebyte(0x00);
    spi_soft_writebyte(0x03);
    spi_soft_writebyte(0x00);
    spi_cs(1);
    tmp_udelay(2);
    spi_cs(0);
    spi_soft_writebyte(0x0f);
    spi_soft_writebyte(0x00);
    spi_soft_writebyte(0x00);
    spi_soft_writebyte(0x01);
    spi_soft_writebyte(0x00);
    spi_cs(1);
    tmp_udelay(2);
    spi_cs(0);
    spi_soft_writebyte(0x18);
    spi_cs(1);
    tmp_udelay(2);
}
static volatile u8 def_cam[7] = {0x10, 0xf0, 0x90, 0x30, 0xb0, 0x80, 0x0};
AT_RAM
void ze_flash_cam_patch()
{
    u32 pgm_polling_ready_cnt = 0x0001ffff;

    ze_entry_tm();

    // PGM   CAM
    tmp_udelay(2);
    spi_cs(0);
    spi_soft_writebyte(0x0f);
    spi_soft_writebyte(0x00);
    spi_soft_writebyte(0x00);
    spi_soft_writebyte(0x03);
    spi_soft_writebyte(0x40);
    spi_cs(1);
    tmp_udelay(2);

    for (u8 i = 0; i < 7; i++) {
        spi_cs(0);
        spi_soft_writebyte(0x06);
        spi_cs(1);


        tmp_udelay(2);
        spi_cs(0);
        spi_soft_writebyte(0x02);
        spi_soft_writebyte(0x00);
        spi_soft_writebyte(0x00);
        spi_soft_writebyte(i);
        spi_soft_writebyte(def_cam[i]);

        spi_cs(1);
        tmp_udelay(2);

        // wait idle
        spi_cs(0);
        while (1) {
            spi_soft_writebyte(0x05);
            u8 pgm_ready = spi_soft_readbyte();
            if (pgm_ready == 0) {
                break;
            }
            pgm_polling_ready_cnt--;
            if (pgm_polling_ready_cnt == 0) {
                JL_CLOCK->PWR_CON |= (1 << 4);
            }
        }
        spi_cs(1);

        tmp_udelay(2);
    }

    ze_exit_tm();
}

AT_RAM
void check_flash_type(void)
{
    if ((flash_info.flash_id == 0x5e3213) || (flash_info.flash_id == 0x5e3214)) {

        tmp_udelay(20);

        ze_flash_cam_patch();

        ze_entry_tm();

        spi_cs(0);
        spi_soft_writebyte(0x0f);
        spi_soft_writebyte(0x00);
        spi_soft_writebyte(0x00);
        spi_soft_writebyte(0x02);
        spi_soft_writebyte(0x70); // 进入补丁模式

        spi_cs(1);
        tmp_udelay(4);
        spi_cs(0);
        spi_soft_writebyte(0x18);
        spi_cs(1);
        tmp_udelay(4);
    }
}

AT_RAM
u8 check_ze_flash_id(void)
{
    if (flash_info.flash_id == 0) {
        read_flash_id();
    }
    if ((flash_info.flash_id == 0x5e3213) || (flash_info.flash_id == 0x5e3214)) {
        return 1;
    }

    return 0;
}

AT_SPI_CODE
static void norflash_entry_sleep(struct device *device)
{
    enter_spi_code();
    u32 is_no_cmd = exit_continue_mode();

    spi_cs(0);
    spi_writebyte(0xb9);
    spi_cs(1);

    if (is_no_cmd) {
        enter_continue_mode();
    }
    exit_spi_code(0);
}
AT_SPI_CODE
static void norflash_exit_sleep(struct device *device)
{
    enter_spi_code();
    u32 is_no_cmd = exit_continue_mode();
    spi_cs(0);
    spi_writebyte(0xab);
    spi_cs(1);
    if (is_no_cmd) {
        enter_continue_mode();
    }
    exit_spi_code(0);
}

static u32 _pow(u32 num, int n)
{
    u32 powint = 1;
    int i;
    for (i = 1; i <= n; i++) {
        powint *= num;
    }
    return powint;
}

static void sfcflash_vm_info_set(void *_arg)
{
    ASSERT(_arg != NULL);

    BOOT_INFO *info = (BOOT_INFO *)_arg;

    memcpy_s(&(flash_info.vm), sizeof(struct vm_info), &(info->vm), sizeof(struct vm_info));
    memcpy_s(&(flash_info.sfc), sizeof(struct sfc_info), &(info->sfc), sizeof(struct sfc_info));

    flash_info.flash_size = info->flash_size;

    JL_SFCENC->UNENC_ADRL = flash_addr2cpu_addr(flash_info.vm.vm_saddr);
    JL_SFCENC->UNENC_ADRH = FLASH_CPU_ADDR_END;        // UNCODE_END_ADD;

    JL_SFCENC->CON |= BIT(1);  // enable sfc uncode

    flushinv_dcache_api(flash_info.vm.vm_saddr, 32);
}

int norflash_ioctl(struct device *device, u32 cmd, u32 arg)
{
    sfc_norflash_mutex_enter();
    int ret = 0;

    switch (cmd) {
        case IOCTL_GET_SECTOR_SIZE:
            *((int *)arg) = 256;
            break;
        case IOCTL_GET_BLOCK_SIZE:
            *((int *)arg) = 64 * 1024;
            break;

        case IOCTL_GET_CAPACITY:
            if (flash_info.flash_capacity == 0) {
                read_flash_id();
                flash_info.flash_capacity = 64 * _pow(2, (flash_info.flash_id & 0xff) - 0x10) * 1024;
            }
            *((int *)arg) = flash_info.flash_capacity;
            break;

        case IOCTL_GET_ID:
            if (flash_info.flash_id == 0) {
                read_flash_id();
                flash_info.flash_size = 64 * _pow(2, (flash_info.flash_id & 0xff) - 0x10) * 1024;
            }
            *((int *)arg) = flash_info.flash_id;
            break;

        case IOCTL_ERASE_PAGE:
        case IOCTL_ERASE_SECTOR:
        case IOCTL_ERASE_BLOCK:
            ret = norflash_erase(cmd, arg);
            break;

        case IOCTL_SET_ENC_END:
            break;

        case IOCTL_SET_VM_INFO:
            sfcflash_vm_info_set((void *)arg);
            break;

        case IOCTL_GET_VM_INFO:
            memcpy_s((u8 *)arg, sizeof(struct vm_info), (u8 *) & (flash_info.vm), sizeof(struct vm_info));
            break;

        default :
            ret = -22;
            break;
    }

    sfc_norflash_mutex_exit();
    return ret;
}

